#
# Ronin Exploits - A Ruby library for Ronin that provides exploitation and
# payload crafting functionality.
#
# Copyright (c) 2007-2013 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of Ronin Exploits.
#
# Ronin is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ronin.  If not, see <http://www.gnu.org/licenses/>
#

require 'ronin/database/migrations/advisory'
require 'ronin/exploits/reference'
require 'ronin/model'

require 'uri/http'
require 'uri/https'
require 'uri/query_params'

module Ronin
  #
  # Represents a vulnerability Advisory, with a Publisher, Number and
  # URL.
  #
  # @since 1.0.0
  #
  class Advisory

    include Model

    # URL generators for various Advisory publishers
    URLS = {
      'CVE' => proc { |year,number|
        "https://web.nvd.nist.gov/view/vuln/detail?vulnId=#{year}-#{number}"
      },
      'OSVDB' => proc { |year,number|
        "https://osvdb.org/show/osvdb/#{number}"
      }
    }

    # Regexp for parsing Advisory identifiers
    REGEXP = /([A-Z][A-Z0-9]+)-(?:(\d+)-)?(\d+)/

    # Primary key of the advisory
    property :id, Serial

    # The publisher of the advisory
    property :publisher, String, required:     true,
                                 format:       /^[A-Z][A-Z0-9]+$/,
                                 unique_index: :publisher_version_number

    # The year the advisory was published in
    property :year, Integer, unique_index: :publisher_version_number

    # The number for the advisory
    property :number, Integer, required:     true,
                               unique_index: :publisher_version_number

    # The references to the advisory
    has 0..n, :references, model: 'Ronin::Exploits::Reference'

    # The exploits for the advisory
    has 0..n, :exploits, through: :references,
                         model:   'Ronin::Exploits::Exploit'

    #
    # Splits an Advisory identifier String.
    #
    # @param [String] identifier
    #   The identifier String to split.
    #
    # @return [Array<publisher, year, number>]
    #   The publisher, year and number for the Advisory.
    #
    # @example
    #   Advisory.split('CVE-2011-1337')
    #   # => ["CVE", "2011", "1337"]
    #
    # @example
    #   Advisory.split('OSVDB-1337')
    #   # => ["OSVDB", nil, "1337"]
    #
    # @api semipublic
    #
    def self.split(identifier)
      if (match = identifier.match(REGEXP))
        [match[1], (match[2].to_i if match[2]), match[3].to_i]
      end
    end

    #
    # Parses an Advisory identifier String.
    #
    # @param [String] identifier
    #   The identifier String for the advisory.
    #
    # @return [Advisory]
    #   The new advisory.
    #
    # @api public
    #
    def self.parse(identifier)
      publisher, year, number = split(identifier)

      return first_or_new(
        publisher: publisher,
        year:      year,
        number:    number
      )
    end

    #
    # Searches for an Advisory.
    #
    # @param [String] identifier
    #   The identifier String for the advisory.
    #
    # @return [Advisory, nil]
    #   The matching advisory.
    #
    # @example
    #   Advisory['CVE-2011-1337']
    #   # => #<Ronin::Advisory: CVE-2011-1337>
    #
    # @api public
    #
    def self.[](identifier)
      publisher, year, number = split(identifier)

      return first(
        publisher: publisher,
        year:      year,
        number:    number
      )
    end

    #
    # Generates a URL for the advisory.
    #
    # @return [String]
    #   The URL for the advisory.
    #
    # @api public
    #
    def url
      if URLS.has_key?(self.publisher)
        URLS[self.publisher][self.year,self.number]
      end
    end

    #
    # Converts the advisory to a String.
    #
    # @return [String]
    #   The publisher and number of the advisory.
    #
    # @api public
    #
    def to_s
      if self.year
        "#{self.publisher}-#{self.year}-#{self.number}"
      else
        "#{self.publisher}-#{self.number}"
      end
    end

    #
    # Coerces the advisory into an Array.
    #
    # @return [Array<publisher, year, number>]
    #   The publisher, year and number of the advisory.
    #
    # @api public
    #
    def to_ary
      [self.publisher, self.year, self.number]
    end

    #
    # Inspects the advisory.
    #
    # @return [String]
    #   The inspected advisory.
    #
    # @api semipublic
    #
    def inspect
      "#<#{self.class}: #{self}>"
    end

  end
end
